Goal: Dan suggested skipping the PCA step and just looking for metabolites associated with leaf length via penalized regression.

library(glmnet)
Loading required package: Matrix
Loaded glmnet 4.1
library(relaimpo)
Loading required package: MASS
Loading required package: boot
Loading required package: survey
Loading required package: grid
Loading required package: survival

Attaching package: ‘survival’

The following object is masked from ‘package:boot’:

    aml


Attaching package: ‘survey’

The following object is masked from ‘package:graphics’:

    dotchart

Loading required package: mitools
This is the global version of package relaimpo.

If you are a non-US user, a version with the interesting additional metric pmvd is available

from Ulrike Groempings web site at prof.beuth-hochschule.de/groemping.
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ ggplot2 3.3.3     ✓ purrr   0.3.4
✓ tibble  3.0.6     ✓ dplyr   1.0.4
✓ tidyr   1.1.2     ✓ stringr 1.4.0
✓ readr   1.4.0     ✓ forcats 0.5.1
── Conflicts ───────────────────────────────────────────────── tidyverse_conflicts() ──
x tidyr::expand() masks Matrix::expand()
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
x tidyr::pack()   masks Matrix::pack()
x dplyr::select() masks MASS::select()
x tidyr::unpack() masks Matrix::unpack()
library(broom)

get leaflength data

leaflength <- read_csv("../../plant/output/leaf_lengths_metabolite.csv") %>%
  mutate(pot=str_pad(pot, width=3, pad="0"),
         sampleID=str_c("wyo", genotype, pot, sep="_")) %>%
  select(sampleID, genotype, trt, leaf_avg_std)

── Column specification ───────────────────────────────────────────────────────────────
cols(
  pot = col_double(),
  soil = col_character(),
  genotype = col_character(),
  trt = col_character(),
  leaf_avg = col_double(),
  leaf_avg_std = col_double()
)
leaflength %>% arrange(sampleID)

get and wrangle metabolite data

met_raw <-read_csv("../input/metabolites_set1.csv")

── Column specification ───────────────────────────────────────────────────────────────
cols(
  .default = col_double(),
  tissue = col_character(),
  soil = col_character(),
  genotype = col_character(),
  autoclave = col_character(),
  time_point = col_character(),
  concatenate = col_character()
)
ℹ Use `spec()` for the full column specifications.
met <- met_raw %>% 
  mutate(pot=str_pad(pot, width = 3, pad = "0")) %>%
  mutate(sampleID=str_c("wyo", genotype, pot, sep="_")) %>%
  select(sampleID, genotype, tissue, sample_mass = `sample_mass mg`, !submission_number:concatenate) %>%
  pivot_longer(!sampleID:sample_mass, names_to = "metabolite", values_to = "met_amount") %>%
  
  #adjust by sample mass
  mutate(met_per_mg=met_amount/sample_mass) %>%
  
  #scale and center
  group_by(metabolite, genotype, tissue) %>%
  mutate(met_per_mg=scale(met_per_mg),
         met_amt=scale(met_amount)
  ) %>% 
  pivot_wider(id_cols = sampleID, 
              names_from = c(tissue, metabolite), 
              values_from = starts_with("met_"),
              names_sep = "_")

met 

split this into two data frames, one normalized by tissue amount and one not.

met_per_mg <- met %>% select(sampleID,  starts_with("met_per_mg")) %>%
  as.data.frame() %>% column_to_rownames("sampleID")
met_amt <- met %>% select(sampleID,  starts_with("met_amt")) %>%
  as.data.frame() %>% column_to_rownames("sampleID")

get leaf data order to match

leaflength <- leaflength[match(met$sampleID, leaflength$sampleID),]
leaflength

now try these in a penalized regression

normalized

multi CV

Fit 101 CVs for each of 11 alphas

set.seed(1245)

folds <- tibble(run=1:101) %>% 
  mutate(folds=map(run, ~ sample(rep(1:6,6))))

system.time (met_per_mg_multiCV <- expand_grid(run=1:100, alpha=round(seq(0,1,.1),1)) %>%
               left_join(folds, by="run") %>%
               mutate(fit=map2(folds, alpha, ~ cv.glmnet(x=as.matrix(met_per_mg),
                                                         y=leaflength$leaf_avg_std, 
                                                         foldid = .x, alpha=.y
                                                         )))
             #, lambda=exp(seq(-5,0,length.out = 50)) )))
) #100 seconds
   user  system elapsed 
194.727  32.263 242.800 
head(met_per_mg_multiCV)

for each fit, pull out the mean cv error, lambda, min lambda, and 1se lambda

met_per_mg_multiCV <- met_per_mg_multiCV %>%
  mutate(cvm=map(fit, magrittr::extract("cvm")),
         lambda=map(fit, magrittr::extract("lambda")),
         lambda.min=map_dbl(fit, magrittr::extract("lambda.min" )),
         lambda.1se=map_dbl(fit, magrittr::extract("lambda.1se")),
         nzero=map(fit, magrittr::extract("nzero"))
  )

head(met_per_mg_multiCV)

now calculate the mean and sem of cvm and min,1se labmdas. These need to be done separately because of the way the grouping works

met_per_mg_summary_cvm <- met_per_mg_multiCV %>% dplyr::select(-fit, -folds) %>% 
  unnest(c(cvm, lambda)) %>%
  group_by(alpha, lambda) %>%
  summarize(meancvm=mean(cvm), sem=sd(cvm)/sqrt(n()), high=meancvm+sem, low=meancvm-sem)
`summarise()` has grouped output by 'alpha'. You can override using the `.groups` argument.
met_per_mg_summary_cvm
met_per_mg_summary_lambda <- met_per_mg_multiCV %>% dplyr::select(-fit, -folds, -cvm) %>% 
  group_by(alpha) %>%
  summarize(
    lambda.min.sd=sd(lambda.min), 
    lambda.min.mean=mean(lambda.min),
    #lambda.min.med=median(lambda.min), 
    lambda.min.high=lambda.min.mean+lambda.min.sd,
    #lambda.min.low=lambda.min.mean-lambda.min.sem,
    #lambda.1se.sem=sd(lambda.1se)/sqrt(n()), 
    lambda.1se.mean=mean(lambda.1se),
    #lambda.1se.med=median(lambda.1se), 
    #lambda.1se.high=lambda.1se+lambda.1se.sem,
    #lambda.1se.low=lambda.1se-lambda.1se.sem,
    nzero=nzero[1],
    lambda=lambda[1]
  )

met_per_mg_summary_lambda

plot it

met_per_mg_summary_cvm %>%
  #filter(alpha!=0) %>% # worse than everything else and throwing the plots off
  ggplot(aes(x=log(lambda), y= meancvm,  ymin=low, ymax=high)) +
  geom_ribbon(alpha=.25) +
  geom_line(aes(color=as.character(alpha))) +
  facet_wrap(~ as.character(alpha)) +
   coord_cartesian(xlim=(c(-5,0))) +
  geom_vline(aes(xintercept=log(lambda.min.mean)), alpha=.5, data=met_per_mg_summary_lambda) +
  geom_vline(aes(xintercept=log(lambda.min.high)), alpha=.5, data=met_per_mg_summary_lambda, color="blue") 

Make a plot of MSE at minimum lambda for each alpha

met_per_mg_summary_cvm %>% 
  group_by(alpha) %>%
  filter(rank(meancvm, ties.method = "first")==1) %>%
  ggplot(aes(x=alpha,y=meancvm,ymin=low,ymax=high)) +
  geom_ribbon(color=NA, fill="gray80") +
  geom_line() +
  geom_point()

Plot the number of nzero coefficients

met_per_mg_summary_lambda %>%
  unnest(c(lambda, nzero)) %>%
  group_by(alpha) %>%
  filter(abs(lambda.min.mean-lambda)==min(abs(lambda.min.mean-lambda))  ) %>%
  ungroup() %>%

ggplot(aes(x=as.character(alpha), y=nzero)) +
  geom_point() +
  ggtitle("Number of non-zero coefficents at minimum lambda") +
  ylim(0,36)

OK let’s do repeated test train starting from these CV lambdas

multi_tt <- function(lambda, alpha, n=10000, sample_size=36, train_size=30, x, y=leaflength$leaf_avg_std) {
  print(lambda)
  print(alpha)
tt <-
  tibble(run=1:n) %>%
  mutate(train=map(run, ~ sample(1:sample_size, train_size))) %>%
  mutate(fit=map(train, ~ glmnet(x=x[.,], y=y[.], lambda = lambda, alpha = alpha ))) %>%
  
  mutate(pred=map2(fit, train, ~ predict(.x, newx = x[-.y,]))) %>%
  mutate(cor=map2_dbl(pred, train, ~ cor(.x, y[-.y])  )) %>%
  mutate(MSE=map2_dbl(pred, train, ~ mean((y[-.y] - .x)^2))) %>%
  summarize(
    num_na=sum(is.na(cor)), 
    num_lt_0=sum(cor<=0, na.rm=TRUE),
    avg_cor=mean(cor, na.rm=TRUE),
    avg_MSE=mean(MSE))
tt
}

per_mg_fit_test_train <- met_per_mg_summary_lambda %>% 
  select(alpha, lambda.min.mean)

per_mg_fit_test_train <- met_per_mg_multiCV %>%
  filter(run==1) %>%
  select(alpha, fit) %>%
  right_join(per_mg_fit_test_train)
Joining, by = "alpha"
per_mg_fit_test_train <- per_mg_fit_test_train %>%
  mutate(pred_full=map2(fit, lambda.min.mean, ~ predict(.x, s=.y, newx=as.matrix(met_per_mg))),
         full_R=map_dbl(pred_full, ~ cor(.x, leaflength$leaf_avg_std)),
         full_MSE=map_dbl(pred_full, ~ mean((leaflength$leaf_avg_std-.x)^2))) %>%
  
  mutate(tt=map2(lambda.min.mean, alpha, ~ multi_tt(lambda=.x, alpha=.y, x=as.matrix(met_per_mg))))
[1] 53.31973
[1] 0
[1] 1.526197
[1] 0.1
[1] 1.083632
[1] 0.2
[1] 0.8767108
[1] 0.3
[1] 0.7482567
[1] 0.4
[1] 0.6440281
[1] 0.5
[1] 0.560638
[1] 0.6
[1] 0.5124209
[1] 0.7
[1] 0.4670289
[1] 0.8
[1] 0.4206258
[1] 0.9
[1] 0.3858907
[1] 1
(per_mg_fit_test_train <- per_mg_fit_test_train %>% unnest(tt))
per_mg_fit_test_train %>%
  ggplot(aes(x=alpha)) +
  geom_line(aes(y=avg_cor), color="red") +
  geom_point(aes(y=avg_cor), color="red") +
  geom_line(aes(y=avg_MSE), color="blue") +
  geom_point(aes(y=avg_MSE), color="blue")

look at fit:

alpha_per_mg <- .5

best_per_mg <- per_mg_fit_test_train %>% filter(alpha == alpha_per_mg) 
best_per_mg_fit <- best_per_mg$fit[[1]]
best_per_mg_lambda <- best_per_mg$lambda.min.mean

per_mg_coef.tb <- coef(best_per_mg_fit, s=best_per_mg_lambda) %>% 
  as.matrix() %>% as.data.frame() %>% 
  rownames_to_column(var="PC") %>%
  rename(beta=`1`)
  
per_mg_coef.tb %>% filter(beta!=0) %>% arrange(beta)
NA

STOPPED HERE

pred and obs

plot(leaflength$leaf_avg_std, best_per_mg$pred_full[[1]])

cor.test(leaflength$leaf_avg_std, best_per_mg$pred_full[[1]]) #.57

    Pearson's product-moment correlation

data:  leaflength$leaf_avg_std and best_per_mg$pred_full[[1]]
t = 7.6535, df = 34, p-value = 6.765e-09
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.6320984 0.8911068
sample estimates:
      cor 
0.7954463 
best_per_mg$full_MSE
[1] 0.6345576

Percent variance explained

per_mg_vars <- per_mg_coef.tb %>% 
  filter(beta !=0, PC!="(Intercept)") %>%
  pull(PC) %>% c("leaf_avg_std", .)

per_mg_relimp <- leaflength %>% select(leaf_avg_std) %>% cbind(met_per_mg.PCs) %>% as.data.frame() %>% dplyr::select(all_of(per_mg_vars)) %>%
  calc.relimp() 

per_mg_coef.tb <- per_mg_relimp@lmg %>% as.matrix() %>% as.data.frame() %>%
  rownames_to_column("PC") %>%
  rename(PropVar_met_per_mg=V1) %>%
  full_join(per_mg_coef.tb) %>%
  arrange(desc(PropVar_met_per_mg))

per_mg_coef.tb

test PCs for sig assoc with trt

leaves

lm with gt and trt

lmtest <- met_per_mg.leaf_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  mutate(trt=ifelse(str_detect(trt, "dead|BLANK"), "deadBLANK", trt)) %>%
  pivot_longer(cols=starts_with("PC"), names_to = "PC") %>%
  mutate(PC=str_c("leaf_", PC)) %>%
  group_by(PC) %>%
  nest() %>%
  mutate(lm_add=map(data, ~ lm(value ~ genotype + trt, data=.)),
         lm_int=map(data, ~ lm(value ~ genotype*trt, data=.)))
lmtest %>% mutate(broomtidy = map(lm_add, tidy)) %>%
  unnest(broomtidy) %>%
  select(PC, term, p.value) %>%
  filter(! str_detect(term, "Intercept"),
         p.value < 0.1) %>%
  arrange(term, p.value)
lmtest %>% mutate(broomtidy = map(lm_int, broom::tidy)) %>%
  unnest(broomtidy) %>%
  select(PC, term, p.value) %>%
  filter(! str_detect(term, "Intercept"),
         p.value < 0.1) %>%
  arrange(term, p.value)

root

lmtest <- met_per_mg.root_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  mutate(trt=ifelse(str_detect(trt, "dead|BLANK"), "deadBLANK", trt)) %>%
  pivot_longer(cols=starts_with("PC"), names_to = "PC") %>%
  mutate(PC=str_c("root_", PC)) %>%
  group_by(PC) %>%
  nest() %>%
  mutate(lm_add=map(data, ~ lm(value ~ genotype + trt, data=.)),
         lm_int=map(data, ~ lm(value ~ genotype*trt, data=.)))
lmtest %>% mutate(broomtidy = map(lm_add, tidy)) %>%
  unnest(broomtidy) %>%
  select(PC, term, p.value) %>%
  filter(! str_detect(term, "Intercept"),
         p.value < 0.1) %>%
  arrange(term, p.value)
lmtest %>% mutate(broomtidy = map(lm_int, broom::tidy)) %>%
  unnest(broomtidy) %>%
  select(PC, term, p.value) %>%
  filter(! str_detect(term, "Intercept"),
         p.value < 0.1) %>%
  arrange(term, p.value)

Checkout the rotations.

met_per_mg_rotation_out <- met_per_mg.PC_rotation %>% 
  pivot_longer(-metabolite, names_to="PC", values_to="loading") %>%
  filter(PC %in% filter(per_mg_coef.tb, beta!=0)$PC ) %>%
  group_by(PC) %>%
    filter(!str_detect(metabolite,".*(leaf|root)_[0-9]*$")) %>%
  filter(abs(loading) >= 0.05) %>%
  left_join(per_mg_coef.tb, by="PC") %>%
  arrange(desc(abs(beta)), desc(abs(loading))) %>%
  mutate(organ=ifelse(str_detect(metabolite, "_leaf_"), "leaf", "root"),
         transformation="normalized",
         metabolite=str_remove(metabolite, "met_per_mg_(root|leaf)_"),
         metabolite_effect_on_leaf=ifelse(beta*loading>0, "increase", "decrease"))
met_per_mg_rotation_out %>%  write_csv("../output/Leaf_associated_metabolites_normalized.csv")

met_per_mg_rotation_out

non-normazlized

multi CV

Fit 101 CVs for each of 11 alphas

set.seed(1245)

folds <- tibble(run=1:101) %>% 
  mutate(folds=map(run, ~ sample(rep(1:6,6))))

system.time (met_amt_multiCV <- expand_grid(run=1:100, alpha=round(seq(0,1,.1),1)) %>%
               left_join(folds, by="run") %>%
               mutate(fit=map2(folds, alpha, ~ cv.glmnet(x=met_amt.PCs, y=leaflength$leaf_avg_std, foldid = .x, alpha=.y
                                                         )))
             #, lambda=exp(seq(-5,0,length.out = 50)) )))
) #100 seconds

head(met_amt_multiCV)

for each fit, pull out the mean cv error, lambda, min lambda, and 1se lambda

met_amt_multiCV <- met_amt_multiCV %>%
  mutate(cvm=map(fit, magrittr::extract("cvm")),
         lambda=map(fit, magrittr::extract("lambda")),
         lambda.min=map_dbl(fit, magrittr::extract("lambda.min" )),
         lambda.1se=map_dbl(fit, magrittr::extract("lambda.1se")),
         nzero=map(fit, magrittr::extract("nzero"))
  )

head(met_amt_multiCV)

now calculate the mean and sem of cvm and min,1se labmdas. These need to be done separately because of the way the grouping works

met_amt_summary_cvm <- met_amt_multiCV %>% dplyr::select(-fit, -folds) %>% 
  unnest(c(cvm, lambda)) %>%
  group_by(alpha, lambda) %>%
  summarize(meancvm=mean(cvm), sem=sd(cvm)/sqrt(n()), high=meancvm+sem, low=meancvm-sem)

met_amt_summary_cvm
met_amt_summary_lambda <- met_amt_multiCV %>% dplyr::select(-fit, -folds, -cvm) %>% 
  group_by(alpha) %>%
  summarize(
    lambda.min.sd=sd(lambda.min), 
    lambda.min.mean=mean(lambda.min),
    #lambda.min.med=median(lambda.min), 
    lambda.min.high=lambda.min.mean+lambda.min.sd,
    #lambda.min.low=lambda.min.mean-lambda.min.sem,
    #lambda.1se.sem=sd(lambda.1se)/sqrt(n()), 
    lambda.1se.mean=mean(lambda.1se),
    #lambda.1se.med=median(lambda.1se), 
    #lambda.1se.high=lambda.1se+lambda.1se.sem,
    #lambda.1se.low=lambda.1se-lambda.1se.sem,
    nzero=nzero[1],
    lambda=lambda[1]
  )

met_amt_summary_lambda

plot it

met_amt_summary_cvm %>%
  #filter(alpha!=0) %>% # worse than everything else and throwing the plots off
  ggplot(aes(x=log(lambda), y= meancvm,  ymin=low, ymax=high)) +
  geom_ribbon(alpha=.25) +
  geom_line(aes(color=as.character(alpha))) +
  facet_wrap(~ as.character(alpha)) +
   coord_cartesian(xlim=(c(-5,0))) +
  geom_vline(aes(xintercept=log(lambda.min.mean)), alpha=.5, data=met_amt_summary_lambda) +
  geom_vline(aes(xintercept=log(lambda.min.high)), alpha=.5, data=met_amt_summary_lambda, color="blue") 

Make a plot of MSE at minimum lambda for each alpha

met_amt_summary_cvm %>% 
  group_by(alpha) %>%
  filter(rank(meancvm, ties.method = "first")==1) %>%
  ggplot(aes(x=alpha,y=meancvm,ymin=low,ymax=high)) +
  geom_ribbon(color=NA, fill="gray80") +
  geom_line() +
  geom_point()

not a particular large difference here after 0.2

Plot the number of nzero coefficients

met_amt_summary_lambda %>%
  unnest(c(lambda, nzero)) %>%
  group_by(alpha) %>%
  filter(abs(lambda.min.mean-lambda)==min(abs(lambda.min.mean-lambda))  ) %>%
  ungroup() %>%

ggplot(aes(x=as.character(alpha), y=nzero)) +
  geom_point() +
  ggtitle("Number of non-zero coefficents at minimum lambda") +
  ylim(0,36)

OK let’s do repeated test train starting from these CV lambdas

multi_tt <- function(lambda, alpha, n=10000, sample_size=36, train_size=30, x, y=leaflength$leaf_avg_std) {
  print(lambda)
  print(alpha)
tt <-
  tibble(run=1:n) %>%
  mutate(train=map(run, ~ sample(1:sample_size, train_size))) %>%
  mutate(fit=map(train, ~ glmnet(x=x[.,], y=y[.], lambda = lambda, alpha = alpha ))) %>%
  
  mutate(pred=map2(fit, train, ~ predict(.x, newx = x[-.y,]))) %>%
  mutate(cor=map2_dbl(pred, train, ~ cor(.x, y[-.y])  )) %>%
  mutate(MSE=map2_dbl(pred, train, ~ mean((y[-.y] - .x)^2))) %>%
  summarize(
    num_na=sum(is.na(cor)), 
    num_lt_0=sum(cor<=0, na.rm=TRUE),
    avg_cor=mean(cor, na.rm=TRUE),
    avg_MSE=mean(MSE))
tt
}

amt_fit_test_train <- met_amt_summary_lambda %>% 
  select(alpha, lambda.min.mean)

amt_fit_test_train <- met_amt_multiCV %>%
  filter(run==1) %>%
  select(alpha, fit) %>%
  right_join(amt_fit_test_train)

amt_fit_test_train <- amt_fit_test_train %>%
  mutate(pred_full=map2(fit, lambda.min.mean, ~ predict(.x, s=.y, newx=met_amt.PCs)),
         full_R=map_dbl(pred_full, ~ cor(.x, leaflength$leaf_avg_std)),
         full_MSE=map_dbl(pred_full, ~ mean((leaflength$leaf_avg_std-.x)^2))) %>%
  
  mutate(tt=map2(lambda.min.mean, alpha, ~ multi_tt(lambda=.x, alpha=.y, x=met_amt.PCs)))



(amt_fit_test_train <- amt_fit_test_train %>% unnest(tt))
amt_fit_test_train %>%
  ggplot(aes(x=alpha)) +
  geom_line(aes(y=avg_cor), color="red") +
  geom_point(aes(y=avg_cor), color="red") +
  geom_line(aes(y=avg_MSE), color="blue") +
  geom_point(aes(y=avg_MSE), color="blue")

alpha of 0.8 to 1.0 are very similar and are the best here.

look at fit:

alpha_amt <- .8

best_amt <- amt_fit_test_train %>% filter(alpha == alpha_amt) 
best_amt_fit <- best_amt$fit[[1]]
best_amt_lambda <- best_amt$lambda.min.mean

amt_coef.tb <- coef(best_amt_fit, s=best_amt_lambda) %>% 
  as.matrix() %>% as.data.frame() %>% 
  rownames_to_column(var="PC") %>%
  rename(beta=`1`)
  
amt_coef.tb %>% filter(beta!=0) %>% arrange(beta)

pred and obs

plot(leaflength$leaf_avg_std, best_amt$pred_full[[1]])
cor.test(leaflength$leaf_avg_std, best_amt$pred_full[[1]]) #.736
best_amt$full_MSE

Percent variance explained

amt_vars <- amt_coef.tb %>% 
  filter(beta !=0, PC!="(Intercept)") %>%
  pull(PC) %>% c("leaf_avg_std", .)

amt_relimp <- leaflength %>% select(leaf_avg_std) %>% cbind(met_amt.PCs) %>% as.data.frame() %>% dplyr::select(all_of(amt_vars)) %>%
  calc.relimp() 

amt_coef.tb <- amt_relimp@lmg %>% as.matrix() %>% as.data.frame() %>%
  rownames_to_column("PC") %>%
  rename(PropVar_met_amt=V1) %>%
  full_join(amt_coef.tb) %>%
  arrange(desc(PropVar_met_amt))

amt_coef.tb

test PCs for sig assoc with trt

leaves

lm with gt and trt

lmtest <- met_amt.leaf_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  mutate(trt=ifelse(str_detect(trt, "dead|BLANK"), "deadBLANK", trt)) %>%
  pivot_longer(cols=starts_with("PC"), names_to = "PC") %>%
  mutate(PC=str_c("leaf_", PC)) %>%
  group_by(PC) %>%
  nest() %>%
  mutate(lm_add=map(data, ~ lm(value ~ genotype + trt, data=.)),
         lm_int=map(data, ~ lm(value ~ genotype*trt, data=.)))
lmtest %>% mutate(broomtidy = map(lm_add, tidy)) %>%
  unnest(broomtidy) %>%
  select(PC, term, p.value) %>%
  filter(! str_detect(term, "Intercept"),
         p.value < 0.1) %>%
  arrange(term, p.value)
lmtest %>% mutate(broomtidy = map(lm_int, broom::tidy)) %>%
  unnest(broomtidy) %>%
  select(PC, term, p.value) %>%
  filter(! str_detect(term, "Intercept"),
         p.value < 0.1) %>%
  arrange(term, p.value)

root

lmtest <- met_amt.root_PCA$x %>%
  as.data.frame() %>%
  rownames_to_column("sampleID") %>%
  left_join(leaflength) %>%
  select(sampleID, genotype, trt, starts_with("PC")) %>%
  mutate(trt=ifelse(str_detect(trt, "dead|BLANK"), "deadBLANK", trt)) %>%
  pivot_longer(cols=starts_with("PC"), names_to = "PC") %>%
  mutate(PC=str_c("root_", PC)) %>%
  group_by(PC) %>%
  nest() %>%
  mutate(lm_add=map(data, ~ lm(value ~ genotype + trt, data=.)),
         lm_int=map(data, ~ lm(value ~ genotype*trt, data=.)))
lmtest %>% mutate(broomtidy = map(lm_add, tidy)) %>%
  unnest(broomtidy) %>%
  select(PC, term, p.value) %>%
  filter(! str_detect(term, "Intercept"),
         p.value < 0.1) %>%
  arrange(term, p.value)
lmtest %>% mutate(broomtidy = map(lm_int, broom::tidy)) %>%
  unnest(broomtidy) %>%
  select(PC, term, p.value) %>%
  filter(! str_detect(term, "Intercept"),
         p.value < 0.1) %>%
  arrange(term, p.value)

Checkout the rotations.

met_amt_rotation_out <- met_amt.PC_rotation %>% 
  pivot_longer(-metabolite, names_to="PC", values_to="loading") %>%
  filter(PC %in% filter(amt_coef.tb, beta!=0)$PC ) %>%
  group_by(PC) %>%
    filter(!str_detect(metabolite,".*(leaf|root)_[0-9]*$")) %>%
  filter(abs(loading) >= 0.05) %>%
  left_join(amt_coef.tb, by="PC") %>%
  arrange(desc(abs(beta)), desc(abs(loading))) %>%
  mutate(organ=ifelse(str_detect(metabolite, "_leaf_"), "leaf", "root"),
         transformation="raw",
         metabolite=str_remove(metabolite, "met_amt_(root|leaf)_"),
         metabolite_effect_on_leaf=ifelse(beta*loading>0, "increase", "decrease"))
met_amt_rotation_out %>%  write_csv("../output/Leaf_associated_metabolites_raw.csv")

met_amt_rotation_out
LS0tCnRpdGxlOiAiRGlyZWN0IFBlbmFsaXplZCBSZWdyZXNzaW9uLS1NZXRhYm9saXRlcyIKYXV0aG9yOiAiSnVsaW4gTWFsb29mIgpkYXRlOiAiMy8wOS8yMDIxIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCkdvYWw6IERhbiBzdWdnZXN0ZWQgc2tpcHBpbmcgdGhlIFBDQSBzdGVwIGFuZCBqdXN0IGxvb2tpbmcgZm9yIG1ldGFib2xpdGVzIGFzc29jaWF0ZWQgd2l0aCBsZWFmIGxlbmd0aCB2aWEgcGVuYWxpemVkIHJlZ3Jlc3Npb24uCgpgYGB7cn0KbGlicmFyeShnbG1uZXQpCmxpYnJhcnkocmVsYWltcG8pCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KGJyb29tKQpgYGAKCmdldCBsZWFmbGVuZ3RoIGRhdGEKYGBge3J9CmxlYWZsZW5ndGggPC0gcmVhZF9jc3YoIi4uLy4uL3BsYW50L291dHB1dC9sZWFmX2xlbmd0aHNfbWV0YWJvbGl0ZS5jc3YiKSAlPiUKICBtdXRhdGUocG90PXN0cl9wYWQocG90LCB3aWR0aD0zLCBwYWQ9IjAiKSwKICAgICAgICAgc2FtcGxlSUQ9c3RyX2MoInd5byIsIGdlbm90eXBlLCBwb3QsIHNlcD0iXyIpKSAlPiUKICBzZWxlY3Qoc2FtcGxlSUQsIGdlbm90eXBlLCB0cnQsIGxlYWZfYXZnX3N0ZCkKbGVhZmxlbmd0aCAlPiUgYXJyYW5nZShzYW1wbGVJRCkKYGBgCgpnZXQgYW5kIHdyYW5nbGUgbWV0YWJvbGl0ZSBkYXRhCmBgYHtyfQptZXRfcmF3IDwtcmVhZF9jc3YoIi4uL2lucHV0L21ldGFib2xpdGVzX3NldDEuY3N2IikKbWV0IDwtIG1ldF9yYXcgJT4lIAogIG11dGF0ZShwb3Q9c3RyX3BhZChwb3QsIHdpZHRoID0gMywgcGFkID0gIjAiKSkgJT4lCiAgbXV0YXRlKHNhbXBsZUlEPXN0cl9jKCJ3eW8iLCBnZW5vdHlwZSwgcG90LCBzZXA9Il8iKSkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdGlzc3VlLCBzYW1wbGVfbWFzcyA9IGBzYW1wbGVfbWFzcyBtZ2AsICFzdWJtaXNzaW9uX251bWJlcjpjb25jYXRlbmF0ZSkgJT4lCiAgcGl2b3RfbG9uZ2VyKCFzYW1wbGVJRDpzYW1wbGVfbWFzcywgbmFtZXNfdG8gPSAibWV0YWJvbGl0ZSIsIHZhbHVlc190byA9ICJtZXRfYW1vdW50IikgJT4lCiAgCiAgI2FkanVzdCBieSBzYW1wbGUgbWFzcwogIG11dGF0ZShtZXRfcGVyX21nPW1ldF9hbW91bnQvc2FtcGxlX21hc3MpICU+JQogIAogICNzY2FsZSBhbmQgY2VudGVyCiAgZ3JvdXBfYnkobWV0YWJvbGl0ZSwgZ2Vub3R5cGUsIHRpc3N1ZSkgJT4lCiAgbXV0YXRlKG1ldF9wZXJfbWc9c2NhbGUobWV0X3Blcl9tZyksCiAgICAgICAgIG1ldF9hbXQ9c2NhbGUobWV0X2Ftb3VudCkKICApICU+JSAKICBwaXZvdF93aWRlcihpZF9jb2xzID0gc2FtcGxlSUQsIAogICAgICAgICAgICAgIG5hbWVzX2Zyb20gPSBjKHRpc3N1ZSwgbWV0YWJvbGl0ZSksIAogICAgICAgICAgICAgIHZhbHVlc19mcm9tID0gc3RhcnRzX3dpdGgoIm1ldF8iKSwKICAgICAgICAgICAgICBuYW1lc19zZXAgPSAiXyIpCgptZXQgCmBgYAoKc3BsaXQgdGhpcyBpbnRvIHR3byBkYXRhIGZyYW1lcywgb25lIG5vcm1hbGl6ZWQgYnkgdGlzc3VlIGFtb3VudCBhbmQgb25lIG5vdC4KYGBge3J9Cm1ldF9wZXJfbWcgPC0gbWV0ICU+JSBzZWxlY3Qoc2FtcGxlSUQsICBzdGFydHNfd2l0aCgibWV0X3Blcl9tZyIpKSAlPiUKICBhcy5kYXRhLmZyYW1lKCkgJT4lIGNvbHVtbl90b19yb3duYW1lcygic2FtcGxlSUQiKQptZXRfYW10IDwtIG1ldCAlPiUgc2VsZWN0KHNhbXBsZUlELCAgc3RhcnRzX3dpdGgoIm1ldF9hbXQiKSkgJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JSBjb2x1bW5fdG9fcm93bmFtZXMoInNhbXBsZUlEIikKYGBgCgpnZXQgbGVhZiBkYXRhIG9yZGVyIHRvIG1hdGNoCgpgYGB7cn0KbGVhZmxlbmd0aCA8LSBsZWFmbGVuZ3RoW21hdGNoKG1ldCRzYW1wbGVJRCwgbGVhZmxlbmd0aCRzYW1wbGVJRCksXQpsZWFmbGVuZ3RoCmBgYAoKIyMgbm93IHRyeSB0aGVzZSBpbiBhIHBlbmFsaXplZCByZWdyZXNzaW9uCgojIG5vcm1hbGl6ZWQKCiMjIG11bHRpIENWCgpGaXQgMTAxIENWcyBmb3IgZWFjaCBvZiAxMSBhbHBoYXMKYGBge3J9CnNldC5zZWVkKDEyNDUpCgpmb2xkcyA8LSB0aWJibGUocnVuPTE6MTAxKSAlPiUgCiAgbXV0YXRlKGZvbGRzPW1hcChydW4sIH4gc2FtcGxlKHJlcCgxOjYsNikpKSkKCnN5c3RlbS50aW1lIChtZXRfcGVyX21nX211bHRpQ1YgPC0gZXhwYW5kX2dyaWQocnVuPTE6MTAwLCBhbHBoYT1yb3VuZChzZXEoMCwxLC4xKSwxKSkgJT4lCiAgICAgICAgICAgICAgIGxlZnRfam9pbihmb2xkcywgYnk9InJ1biIpICU+JQogICAgICAgICAgICAgICBtdXRhdGUoZml0PW1hcDIoZm9sZHMsIGFscGhhLCB+IGN2LmdsbW5ldCh4PWFzLm1hdHJpeChtZXRfcGVyX21nKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeT1sZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZvbGRpZCA9IC54LCBhbHBoYT0ueQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkKICAgICAgICAgICAgICMsIGxhbWJkYT1leHAoc2VxKC01LDAsbGVuZ3RoLm91dCA9IDUwKSkgKSkpCikgIzEwMCBzZWNvbmRzCgpoZWFkKG1ldF9wZXJfbWdfbXVsdGlDVikKYGBgCgpmb3IgZWFjaCBmaXQsIHB1bGwgb3V0IHRoZSBtZWFuIGN2IGVycm9yLCBsYW1iZGEsIG1pbiBsYW1iZGEsIGFuZCAxc2UgbGFtYmRhIApgYGB7cn0KbWV0X3Blcl9tZ19tdWx0aUNWIDwtIG1ldF9wZXJfbWdfbXVsdGlDViAlPiUKICBtdXRhdGUoY3ZtPW1hcChmaXQsIG1hZ3JpdHRyOjpleHRyYWN0KCJjdm0iKSksCiAgICAgICAgIGxhbWJkYT1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhIikpLAogICAgICAgICBsYW1iZGEubWluPW1hcF9kYmwoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhLm1pbiIgKSksCiAgICAgICAgIGxhbWJkYS4xc2U9bWFwX2RibChmaXQsIG1hZ3JpdHRyOjpleHRyYWN0KCJsYW1iZGEuMXNlIikpLAogICAgICAgICBuemVybz1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibnplcm8iKSkKICApCgpoZWFkKG1ldF9wZXJfbWdfbXVsdGlDVikKYGBgCgoKbm93IGNhbGN1bGF0ZSB0aGUgbWVhbiBhbmQgc2VtIG9mIGN2bSBhbmQgbWluLDFzZSBsYWJtZGFzLiAgVGhlc2UgbmVlZCB0byBiZSBkb25lIHNlcGFyYXRlbHkgYmVjYXVzZSBvZiB0aGUgd2F5IHRoZSBncm91cGluZyB3b3JrcwpgYGB7cn0KbWV0X3Blcl9tZ19zdW1tYXJ5X2N2bSA8LSBtZXRfcGVyX21nX211bHRpQ1YgJT4lIGRwbHlyOjpzZWxlY3QoLWZpdCwgLWZvbGRzKSAlPiUgCiAgdW5uZXN0KGMoY3ZtLCBsYW1iZGEpKSAlPiUKICBncm91cF9ieShhbHBoYSwgbGFtYmRhKSAlPiUKICBzdW1tYXJpemUobWVhbmN2bT1tZWFuKGN2bSksIHNlbT1zZChjdm0pL3NxcnQobigpKSwgaGlnaD1tZWFuY3ZtK3NlbSwgbG93PW1lYW5jdm0tc2VtKQoKbWV0X3Blcl9tZ19zdW1tYXJ5X2N2bQpgYGAKCmBgYHtyfQptZXRfcGVyX21nX3N1bW1hcnlfbGFtYmRhIDwtIG1ldF9wZXJfbWdfbXVsdGlDViAlPiUgZHBseXI6OnNlbGVjdCgtZml0LCAtZm9sZHMsIC1jdm0pICU+JSAKICBncm91cF9ieShhbHBoYSkgJT4lCiAgc3VtbWFyaXplKAogICAgbGFtYmRhLm1pbi5zZD1zZChsYW1iZGEubWluKSwgCiAgICBsYW1iZGEubWluLm1lYW49bWVhbihsYW1iZGEubWluKSwKICAgICNsYW1iZGEubWluLm1lZD1tZWRpYW4obGFtYmRhLm1pbiksIAogICAgbGFtYmRhLm1pbi5oaWdoPWxhbWJkYS5taW4ubWVhbitsYW1iZGEubWluLnNkLAogICAgI2xhbWJkYS5taW4ubG93PWxhbWJkYS5taW4ubWVhbi1sYW1iZGEubWluLnNlbSwKICAgICNsYW1iZGEuMXNlLnNlbT1zZChsYW1iZGEuMXNlKS9zcXJ0KG4oKSksIAogICAgbGFtYmRhLjFzZS5tZWFuPW1lYW4obGFtYmRhLjFzZSksCiAgICAjbGFtYmRhLjFzZS5tZWQ9bWVkaWFuKGxhbWJkYS4xc2UpLCAKICAgICNsYW1iZGEuMXNlLmhpZ2g9bGFtYmRhLjFzZStsYW1iZGEuMXNlLnNlbSwKICAgICNsYW1iZGEuMXNlLmxvdz1sYW1iZGEuMXNlLWxhbWJkYS4xc2Uuc2VtLAogICAgbnplcm89bnplcm9bMV0sCiAgICBsYW1iZGE9bGFtYmRhWzFdCiAgKQoKbWV0X3Blcl9tZ19zdW1tYXJ5X2xhbWJkYQpgYGAKCgpwbG90IGl0CmBgYHtyfQptZXRfcGVyX21nX3N1bW1hcnlfY3ZtICU+JQogICNmaWx0ZXIoYWxwaGEhPTApICU+JSAjIHdvcnNlIHRoYW4gZXZlcnl0aGluZyBlbHNlIGFuZCB0aHJvd2luZyB0aGUgcGxvdHMgb2ZmCiAgZ2dwbG90KGFlcyh4PWxvZyhsYW1iZGEpLCB5PSBtZWFuY3ZtLCAgeW1pbj1sb3csIHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihhbHBoYT0uMjUpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yPWFzLmNoYXJhY3RlcihhbHBoYSkpKSArCiAgZmFjZXRfd3JhcCh+IGFzLmNoYXJhY3RlcihhbHBoYSkpICsKICAgY29vcmRfY2FydGVzaWFuKHhsaW09KGMoLTUsMCkpKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1sb2cobGFtYmRhLm1pbi5tZWFuKSksIGFscGhhPS41LCBkYXRhPW1ldF9wZXJfbWdfc3VtbWFyeV9sYW1iZGEpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PWxvZyhsYW1iZGEubWluLmhpZ2gpKSwgYWxwaGE9LjUsIGRhdGE9bWV0X3Blcl9tZ19zdW1tYXJ5X2xhbWJkYSwgY29sb3I9ImJsdWUiKSAKCmBgYAoKTWFrZSBhIHBsb3Qgb2YgTVNFIGF0IG1pbmltdW0gbGFtYmRhIGZvciBlYWNoIGFscGhhCgpgYGB7cn0KbWV0X3Blcl9tZ19zdW1tYXJ5X2N2bSAlPiUgCiAgZ3JvdXBfYnkoYWxwaGEpICU+JQogIGZpbHRlcihyYW5rKG1lYW5jdm0sIHRpZXMubWV0aG9kID0gImZpcnN0Iik9PTEpICU+JQogIGdncGxvdChhZXMoeD1hbHBoYSx5PW1lYW5jdm0seW1pbj1sb3cseW1heD1oaWdoKSkgKwogIGdlb21fcmliYm9uKGNvbG9yPU5BLCBmaWxsPSJncmF5ODAiKSArCiAgZ2VvbV9saW5lKCkgKwogIGdlb21fcG9pbnQoKQpgYGAKClBsb3QgdGhlIG51bWJlciBvZiBuemVybyBjb2VmZmljaWVudHMKCmBgYHtyfQptZXRfcGVyX21nX3N1bW1hcnlfbGFtYmRhICU+JQogIHVubmVzdChjKGxhbWJkYSwgbnplcm8pKSAlPiUKICBncm91cF9ieShhbHBoYSkgJT4lCiAgZmlsdGVyKGFicyhsYW1iZGEubWluLm1lYW4tbGFtYmRhKT09bWluKGFicyhsYW1iZGEubWluLm1lYW4tbGFtYmRhKSkgICkgJT4lCiAgdW5ncm91cCgpICU+JQoKZ2dwbG90KGFlcyh4PWFzLmNoYXJhY3RlcihhbHBoYSksIHk9bnplcm8pKSArCiAgZ2VvbV9wb2ludCgpICsKICBnZ3RpdGxlKCJOdW1iZXIgb2Ygbm9uLXplcm8gY29lZmZpY2VudHMgYXQgbWluaW11bSBsYW1iZGEiKSArCiAgeWxpbSgwLDM2KQpgYGAKT0sgbGV0J3MgZG8gcmVwZWF0ZWQgdGVzdCB0cmFpbiBzdGFydGluZyBmcm9tIHRoZXNlIENWIGxhbWJkYXMKCmBgYHtyfQptdWx0aV90dCA8LSBmdW5jdGlvbihsYW1iZGEsIGFscGhhLCBuPTEwMDAwLCBzYW1wbGVfc2l6ZT0zNiwgdHJhaW5fc2l6ZT0zMCwgeCwgeT1sZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZCkgewogIHByaW50KGxhbWJkYSkKICBwcmludChhbHBoYSkKdHQgPC0KICB0aWJibGUocnVuPTE6bikgJT4lCiAgbXV0YXRlKHRyYWluPW1hcChydW4sIH4gc2FtcGxlKDE6c2FtcGxlX3NpemUsIHRyYWluX3NpemUpKSkgJT4lCiAgbXV0YXRlKGZpdD1tYXAodHJhaW4sIH4gZ2xtbmV0KHg9eFsuLF0sIHk9eVsuXSwgbGFtYmRhID0gbGFtYmRhLCBhbHBoYSA9IGFscGhhICkpKSAlPiUKICAKICBtdXRhdGUocHJlZD1tYXAyKGZpdCwgdHJhaW4sIH4gcHJlZGljdCgueCwgbmV3eCA9IHhbLS55LF0pKSkgJT4lCiAgbXV0YXRlKGNvcj1tYXAyX2RibChwcmVkLCB0cmFpbiwgfiBjb3IoLngsIHlbLS55XSkgICkpICU+JQogIG11dGF0ZShNU0U9bWFwMl9kYmwocHJlZCwgdHJhaW4sIH4gbWVhbigoeVstLnldIC0gLngpXjIpKSkgJT4lCiAgc3VtbWFyaXplKAogICAgbnVtX25hPXN1bShpcy5uYShjb3IpKSwgCiAgICBudW1fbHRfMD1zdW0oY29yPD0wLCBuYS5ybT1UUlVFKSwKICAgIGF2Z19jb3I9bWVhbihjb3IsIG5hLnJtPVRSVUUpLAogICAgYXZnX01TRT1tZWFuKE1TRSkpCnR0Cn0KCnBlcl9tZ19maXRfdGVzdF90cmFpbiA8LSBtZXRfcGVyX21nX3N1bW1hcnlfbGFtYmRhICU+JSAKICBzZWxlY3QoYWxwaGEsIGxhbWJkYS5taW4ubWVhbikKCnBlcl9tZ19maXRfdGVzdF90cmFpbiA8LSBtZXRfcGVyX21nX211bHRpQ1YgJT4lCiAgZmlsdGVyKHJ1bj09MSkgJT4lCiAgc2VsZWN0KGFscGhhLCBmaXQpICU+JQogIHJpZ2h0X2pvaW4ocGVyX21nX2ZpdF90ZXN0X3RyYWluKQoKcGVyX21nX2ZpdF90ZXN0X3RyYWluIDwtIHBlcl9tZ19maXRfdGVzdF90cmFpbiAlPiUKICBtdXRhdGUocHJlZF9mdWxsPW1hcDIoZml0LCBsYW1iZGEubWluLm1lYW4sIH4gcHJlZGljdCgueCwgcz0ueSwgbmV3eD1hcy5tYXRyaXgobWV0X3Blcl9tZykpKSwKICAgICAgICAgZnVsbF9SPW1hcF9kYmwocHJlZF9mdWxsLCB+IGNvcigueCwgbGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQpKSwKICAgICAgICAgZnVsbF9NU0U9bWFwX2RibChwcmVkX2Z1bGwsIH4gbWVhbigobGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQtLngpXjIpKSkgJT4lCiAgCiAgbXV0YXRlKHR0PW1hcDIobGFtYmRhLm1pbi5tZWFuLCBhbHBoYSwgfiBtdWx0aV90dChsYW1iZGE9LngsIGFscGhhPS55LCB4PWFzLm1hdHJpeChtZXRfcGVyX21nKSkpKQoKCgoocGVyX21nX2ZpdF90ZXN0X3RyYWluIDwtIHBlcl9tZ19maXRfdGVzdF90cmFpbiAlPiUgdW5uZXN0KHR0KSkKYGBgCgpgYGB7cn0KcGVyX21nX2ZpdF90ZXN0X3RyYWluICU+JQogIGdncGxvdChhZXMoeD1hbHBoYSkpICsKICBnZW9tX2xpbmUoYWVzKHk9YXZnX2NvciksIGNvbG9yPSJyZWQiKSArCiAgZ2VvbV9wb2ludChhZXMoeT1hdmdfY29yKSwgY29sb3I9InJlZCIpICsKICBnZW9tX2xpbmUoYWVzKHk9YXZnX01TRSksIGNvbG9yPSJibHVlIikgKwogIGdlb21fcG9pbnQoYWVzKHk9YXZnX01TRSksIGNvbG9yPSJibHVlIikKYGBgCgojIyBsb29rIGF0IGZpdDoKCmBgYHtyfQphbHBoYV9wZXJfbWcgPC0gLjUKCmJlc3RfcGVyX21nIDwtIHBlcl9tZ19maXRfdGVzdF90cmFpbiAlPiUgZmlsdGVyKGFscGhhID09IGFscGhhX3Blcl9tZykgCmJlc3RfcGVyX21nX2ZpdCA8LSBiZXN0X3Blcl9tZyRmaXRbWzFdXQpiZXN0X3Blcl9tZ19sYW1iZGEgPC0gYmVzdF9wZXJfbWckbGFtYmRhLm1pbi5tZWFuCgpwZXJfbWdfY29lZi50YiA8LSBjb2VmKGJlc3RfcGVyX21nX2ZpdCwgcz1iZXN0X3Blcl9tZ19sYW1iZGEpICU+JSAKICBhcy5tYXRyaXgoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSAKICByb3duYW1lc190b19jb2x1bW4odmFyPSJQQyIpICU+JQogIHJlbmFtZShiZXRhPWAxYCkKICAKcGVyX21nX2NvZWYudGIgJT4lIGZpbHRlcihiZXRhIT0wKSAlPiUgYXJyYW5nZShiZXRhKQoKYGBgCgpTVE9QUEVEIEhFUkUKCnByZWQgYW5kIG9icwpgYGB7cn0KcGxvdChsZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZCwgYmVzdF9wZXJfbWckcHJlZF9mdWxsW1sxXV0pCmNvci50ZXN0KGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLCBiZXN0X3Blcl9tZyRwcmVkX2Z1bGxbWzFdXSkgIy41NwpiZXN0X3Blcl9tZyRmdWxsX01TRQpgYGAKCiMjIFBlcmNlbnQgdmFyaWFuY2UgZXhwbGFpbmVkCgpgYGB7cn0KcGVyX21nX3ZhcnMgPC0gcGVyX21nX2NvZWYudGIgJT4lIAogIGZpbHRlcihiZXRhICE9MCwgUEMhPSIoSW50ZXJjZXB0KSIpICU+JQogIHB1bGwoUEMpICU+JSBjKCJsZWFmX2F2Z19zdGQiLCAuKQoKcGVyX21nX3JlbGltcCA8LSBsZWFmbGVuZ3RoICU+JSBzZWxlY3QobGVhZl9hdmdfc3RkKSAlPiUgY2JpbmQobWV0X3Blcl9tZy5QQ3MpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIGRwbHlyOjpzZWxlY3QoYWxsX29mKHBlcl9tZ192YXJzKSkgJT4lCiAgY2FsYy5yZWxpbXAoKSAKCnBlcl9tZ19jb2VmLnRiIDwtIHBlcl9tZ19yZWxpbXBAbG1nICU+JSBhcy5tYXRyaXgoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigiUEMiKSAlPiUKICByZW5hbWUoUHJvcFZhcl9tZXRfcGVyX21nPVYxKSAlPiUKICBmdWxsX2pvaW4ocGVyX21nX2NvZWYudGIpICU+JQogIGFycmFuZ2UoZGVzYyhQcm9wVmFyX21ldF9wZXJfbWcpKQoKcGVyX21nX2NvZWYudGIKCmBgYAoKIyMgdGVzdCBQQ3MgZm9yIHNpZyBhc3NvYyB3aXRoIHRydAoKIyMjIGxlYXZlcwpsbSB3aXRoIGd0IGFuZCB0cnQKYGBge3J9CmxtdGVzdCA8LSBtZXRfcGVyX21nLmxlYWZfUENBJHggJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdHJ0LCBzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgbXV0YXRlKHRydD1pZmVsc2Uoc3RyX2RldGVjdCh0cnQsICJkZWFkfEJMQU5LIiksICJkZWFkQkxBTksiLCB0cnQpKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1zdGFydHNfd2l0aCgiUEMiKSwgbmFtZXNfdG8gPSAiUEMiKSAlPiUKICBtdXRhdGUoUEM9c3RyX2MoImxlYWZfIiwgUEMpKSAlPiUKICBncm91cF9ieShQQykgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZShsbV9hZGQ9bWFwKGRhdGEsIH4gbG0odmFsdWUgfiBnZW5vdHlwZSArIHRydCwgZGF0YT0uKSksCiAgICAgICAgIGxtX2ludD1tYXAoZGF0YSwgfiBsbSh2YWx1ZSB+IGdlbm90eXBlKnRydCwgZGF0YT0uKSkpCgpgYGAKCmBgYHtyfQpsbXRlc3QgJT4lIG11dGF0ZShicm9vbXRpZHkgPSBtYXAobG1fYWRkLCB0aWR5KSkgJT4lCiAgdW5uZXN0KGJyb29tdGlkeSkgJT4lCiAgc2VsZWN0KFBDLCB0ZXJtLCBwLnZhbHVlKSAlPiUKICBmaWx0ZXIoISBzdHJfZGV0ZWN0KHRlcm0sICJJbnRlcmNlcHQiKSwKICAgICAgICAgcC52YWx1ZSA8IDAuMSkgJT4lCiAgYXJyYW5nZSh0ZXJtLCBwLnZhbHVlKQpgYGAKCmBgYHtyfQpsbXRlc3QgJT4lIG11dGF0ZShicm9vbXRpZHkgPSBtYXAobG1faW50LCBicm9vbTo6dGlkeSkpICU+JQogIHVubmVzdChicm9vbXRpZHkpICU+JQogIHNlbGVjdChQQywgdGVybSwgcC52YWx1ZSkgJT4lCiAgZmlsdGVyKCEgc3RyX2RldGVjdCh0ZXJtLCAiSW50ZXJjZXB0IiksCiAgICAgICAgIHAudmFsdWUgPCAwLjEpICU+JQogIGFycmFuZ2UodGVybSwgcC52YWx1ZSkKYGBgCgojIyMgcm9vdAoKYGBge3J9CmxtdGVzdCA8LSBtZXRfcGVyX21nLnJvb3RfUENBJHggJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdHJ0LCBzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgbXV0YXRlKHRydD1pZmVsc2Uoc3RyX2RldGVjdCh0cnQsICJkZWFkfEJMQU5LIiksICJkZWFkQkxBTksiLCB0cnQpKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1zdGFydHNfd2l0aCgiUEMiKSwgbmFtZXNfdG8gPSAiUEMiKSAlPiUKICBtdXRhdGUoUEM9c3RyX2MoInJvb3RfIiwgUEMpKSAlPiUKICBncm91cF9ieShQQykgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZShsbV9hZGQ9bWFwKGRhdGEsIH4gbG0odmFsdWUgfiBnZW5vdHlwZSArIHRydCwgZGF0YT0uKSksCiAgICAgICAgIGxtX2ludD1tYXAoZGF0YSwgfiBsbSh2YWx1ZSB+IGdlbm90eXBlKnRydCwgZGF0YT0uKSkpCgpgYGAKCmBgYHtyfQpsbXRlc3QgJT4lIG11dGF0ZShicm9vbXRpZHkgPSBtYXAobG1fYWRkLCB0aWR5KSkgJT4lCiAgdW5uZXN0KGJyb29tdGlkeSkgJT4lCiAgc2VsZWN0KFBDLCB0ZXJtLCBwLnZhbHVlKSAlPiUKICBmaWx0ZXIoISBzdHJfZGV0ZWN0KHRlcm0sICJJbnRlcmNlcHQiKSwKICAgICAgICAgcC52YWx1ZSA8IDAuMSkgJT4lCiAgYXJyYW5nZSh0ZXJtLCBwLnZhbHVlKQpgYGAKCmBgYHtyfQpsbXRlc3QgJT4lIG11dGF0ZShicm9vbXRpZHkgPSBtYXAobG1faW50LCBicm9vbTo6dGlkeSkpICU+JQogIHVubmVzdChicm9vbXRpZHkpICU+JQogIHNlbGVjdChQQywgdGVybSwgcC52YWx1ZSkgJT4lCiAgZmlsdGVyKCEgc3RyX2RldGVjdCh0ZXJtLCAiSW50ZXJjZXB0IiksCiAgICAgICAgIHAudmFsdWUgPCAwLjEpICU+JQogIGFycmFuZ2UodGVybSwgcC52YWx1ZSkKYGBgCgpDaGVja291dCB0aGUgcm90YXRpb25zLiAgCgoKYGBge3J9Cm1ldF9wZXJfbWdfcm90YXRpb25fb3V0IDwtIG1ldF9wZXJfbWcuUENfcm90YXRpb24gJT4lIAogIHBpdm90X2xvbmdlcigtbWV0YWJvbGl0ZSwgbmFtZXNfdG89IlBDIiwgdmFsdWVzX3RvPSJsb2FkaW5nIikgJT4lCiAgZmlsdGVyKFBDICVpbiUgZmlsdGVyKHBlcl9tZ19jb2VmLnRiLCBiZXRhIT0wKSRQQyApICU+JQogIGdyb3VwX2J5KFBDKSAlPiUKICAgIGZpbHRlcighc3RyX2RldGVjdChtZXRhYm9saXRlLCIuKihsZWFmfHJvb3QpX1swLTldKiQiKSkgJT4lCiAgZmlsdGVyKGFicyhsb2FkaW5nKSA+PSAwLjA1KSAlPiUKICBsZWZ0X2pvaW4ocGVyX21nX2NvZWYudGIsIGJ5PSJQQyIpICU+JQogIGFycmFuZ2UoZGVzYyhhYnMoYmV0YSkpLCBkZXNjKGFicyhsb2FkaW5nKSkpICU+JQogIG11dGF0ZShvcmdhbj1pZmVsc2Uoc3RyX2RldGVjdChtZXRhYm9saXRlLCAiX2xlYWZfIiksICJsZWFmIiwgInJvb3QiKSwKICAgICAgICAgdHJhbnNmb3JtYXRpb249Im5vcm1hbGl6ZWQiLAogICAgICAgICBtZXRhYm9saXRlPXN0cl9yZW1vdmUobWV0YWJvbGl0ZSwgIm1ldF9wZXJfbWdfKHJvb3R8bGVhZilfIiksCiAgICAgICAgIG1ldGFib2xpdGVfZWZmZWN0X29uX2xlYWY9aWZlbHNlKGJldGEqbG9hZGluZz4wLCAiaW5jcmVhc2UiLCAiZGVjcmVhc2UiKSkKbWV0X3Blcl9tZ19yb3RhdGlvbl9vdXQgJT4lICB3cml0ZV9jc3YoIi4uL291dHB1dC9MZWFmX2Fzc29jaWF0ZWRfbWV0YWJvbGl0ZXNfbm9ybWFsaXplZC5jc3YiKQoKbWV0X3Blcl9tZ19yb3RhdGlvbl9vdXQKYGBgCgojIG5vbi1ub3JtYXpsaXplZAoKIyMgbXVsdGkgQ1YKCkZpdCAxMDEgQ1ZzIGZvciBlYWNoIG9mIDExIGFscGhhcwpgYGB7cn0Kc2V0LnNlZWQoMTI0NSkKCmZvbGRzIDwtIHRpYmJsZShydW49MToxMDEpICU+JSAKICBtdXRhdGUoZm9sZHM9bWFwKHJ1biwgfiBzYW1wbGUocmVwKDE6Niw2KSkpKQoKc3lzdGVtLnRpbWUgKG1ldF9hbXRfbXVsdGlDViA8LSBleHBhbmRfZ3JpZChydW49MToxMDAsIGFscGhhPXJvdW5kKHNlcSgwLDEsLjEpLDEpKSAlPiUKICAgICAgICAgICAgICAgbGVmdF9qb2luKGZvbGRzLCBieT0icnVuIikgJT4lCiAgICAgICAgICAgICAgIG11dGF0ZShmaXQ9bWFwMihmb2xkcywgYWxwaGEsIH4gY3YuZ2xtbmV0KHg9bWV0X2FtdC5QQ3MsIHk9bGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIGZvbGRpZCA9IC54LCBhbHBoYT0ueQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApKSkKICAgICAgICAgICAgICMsIGxhbWJkYT1leHAoc2VxKC01LDAsbGVuZ3RoLm91dCA9IDUwKSkgKSkpCikgIzEwMCBzZWNvbmRzCgpoZWFkKG1ldF9hbXRfbXVsdGlDVikKYGBgCgpmb3IgZWFjaCBmaXQsIHB1bGwgb3V0IHRoZSBtZWFuIGN2IGVycm9yLCBsYW1iZGEsIG1pbiBsYW1iZGEsIGFuZCAxc2UgbGFtYmRhIApgYGB7cn0KbWV0X2FtdF9tdWx0aUNWIDwtIG1ldF9hbXRfbXVsdGlDViAlPiUKICBtdXRhdGUoY3ZtPW1hcChmaXQsIG1hZ3JpdHRyOjpleHRyYWN0KCJjdm0iKSksCiAgICAgICAgIGxhbWJkYT1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhIikpLAogICAgICAgICBsYW1iZGEubWluPW1hcF9kYmwoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibGFtYmRhLm1pbiIgKSksCiAgICAgICAgIGxhbWJkYS4xc2U9bWFwX2RibChmaXQsIG1hZ3JpdHRyOjpleHRyYWN0KCJsYW1iZGEuMXNlIikpLAogICAgICAgICBuemVybz1tYXAoZml0LCBtYWdyaXR0cjo6ZXh0cmFjdCgibnplcm8iKSkKICApCgpoZWFkKG1ldF9hbXRfbXVsdGlDVikKYGBgCgoKbm93IGNhbGN1bGF0ZSB0aGUgbWVhbiBhbmQgc2VtIG9mIGN2bSBhbmQgbWluLDFzZSBsYWJtZGFzLiAgVGhlc2UgbmVlZCB0byBiZSBkb25lIHNlcGFyYXRlbHkgYmVjYXVzZSBvZiB0aGUgd2F5IHRoZSBncm91cGluZyB3b3JrcwpgYGB7cn0KbWV0X2FtdF9zdW1tYXJ5X2N2bSA8LSBtZXRfYW10X211bHRpQ1YgJT4lIGRwbHlyOjpzZWxlY3QoLWZpdCwgLWZvbGRzKSAlPiUgCiAgdW5uZXN0KGMoY3ZtLCBsYW1iZGEpKSAlPiUKICBncm91cF9ieShhbHBoYSwgbGFtYmRhKSAlPiUKICBzdW1tYXJpemUobWVhbmN2bT1tZWFuKGN2bSksIHNlbT1zZChjdm0pL3NxcnQobigpKSwgaGlnaD1tZWFuY3ZtK3NlbSwgbG93PW1lYW5jdm0tc2VtKQoKbWV0X2FtdF9zdW1tYXJ5X2N2bQpgYGAKCmBgYHtyfQptZXRfYW10X3N1bW1hcnlfbGFtYmRhIDwtIG1ldF9hbXRfbXVsdGlDViAlPiUgZHBseXI6OnNlbGVjdCgtZml0LCAtZm9sZHMsIC1jdm0pICU+JSAKICBncm91cF9ieShhbHBoYSkgJT4lCiAgc3VtbWFyaXplKAogICAgbGFtYmRhLm1pbi5zZD1zZChsYW1iZGEubWluKSwgCiAgICBsYW1iZGEubWluLm1lYW49bWVhbihsYW1iZGEubWluKSwKICAgICNsYW1iZGEubWluLm1lZD1tZWRpYW4obGFtYmRhLm1pbiksIAogICAgbGFtYmRhLm1pbi5oaWdoPWxhbWJkYS5taW4ubWVhbitsYW1iZGEubWluLnNkLAogICAgI2xhbWJkYS5taW4ubG93PWxhbWJkYS5taW4ubWVhbi1sYW1iZGEubWluLnNlbSwKICAgICNsYW1iZGEuMXNlLnNlbT1zZChsYW1iZGEuMXNlKS9zcXJ0KG4oKSksIAogICAgbGFtYmRhLjFzZS5tZWFuPW1lYW4obGFtYmRhLjFzZSksCiAgICAjbGFtYmRhLjFzZS5tZWQ9bWVkaWFuKGxhbWJkYS4xc2UpLCAKICAgICNsYW1iZGEuMXNlLmhpZ2g9bGFtYmRhLjFzZStsYW1iZGEuMXNlLnNlbSwKICAgICNsYW1iZGEuMXNlLmxvdz1sYW1iZGEuMXNlLWxhbWJkYS4xc2Uuc2VtLAogICAgbnplcm89bnplcm9bMV0sCiAgICBsYW1iZGE9bGFtYmRhWzFdCiAgKQoKbWV0X2FtdF9zdW1tYXJ5X2xhbWJkYQpgYGAKCgpwbG90IGl0CmBgYHtyfQptZXRfYW10X3N1bW1hcnlfY3ZtICU+JQogICNmaWx0ZXIoYWxwaGEhPTApICU+JSAjIHdvcnNlIHRoYW4gZXZlcnl0aGluZyBlbHNlIGFuZCB0aHJvd2luZyB0aGUgcGxvdHMgb2ZmCiAgZ2dwbG90KGFlcyh4PWxvZyhsYW1iZGEpLCB5PSBtZWFuY3ZtLCAgeW1pbj1sb3csIHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihhbHBoYT0uMjUpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yPWFzLmNoYXJhY3RlcihhbHBoYSkpKSArCiAgZmFjZXRfd3JhcCh+IGFzLmNoYXJhY3RlcihhbHBoYSkpICsKICAgY29vcmRfY2FydGVzaWFuKHhsaW09KGMoLTUsMCkpKSArCiAgZ2VvbV92bGluZShhZXMoeGludGVyY2VwdD1sb2cobGFtYmRhLm1pbi5tZWFuKSksIGFscGhhPS41LCBkYXRhPW1ldF9hbXRfc3VtbWFyeV9sYW1iZGEpICsKICBnZW9tX3ZsaW5lKGFlcyh4aW50ZXJjZXB0PWxvZyhsYW1iZGEubWluLmhpZ2gpKSwgYWxwaGE9LjUsIGRhdGE9bWV0X2FtdF9zdW1tYXJ5X2xhbWJkYSwgY29sb3I9ImJsdWUiKSAKCmBgYAoKCk1ha2UgYSBwbG90IG9mIE1TRSBhdCBtaW5pbXVtIGxhbWJkYSBmb3IgZWFjaCBhbHBoYQoKYGBge3J9Cm1ldF9hbXRfc3VtbWFyeV9jdm0gJT4lIAogIGdyb3VwX2J5KGFscGhhKSAlPiUKICBmaWx0ZXIocmFuayhtZWFuY3ZtLCB0aWVzLm1ldGhvZCA9ICJmaXJzdCIpPT0xKSAlPiUKICBnZ3Bsb3QoYWVzKHg9YWxwaGEseT1tZWFuY3ZtLHltaW49bG93LHltYXg9aGlnaCkpICsKICBnZW9tX3JpYmJvbihjb2xvcj1OQSwgZmlsbD0iZ3JheTgwIikgKwogIGdlb21fbGluZSgpICsKICBnZW9tX3BvaW50KCkKYGBgCm5vdCBhIHBhcnRpY3VsYXIgbGFyZ2UgZGlmZmVyZW5jZSBoZXJlIGFmdGVyIDAuMgoKUGxvdCB0aGUgbnVtYmVyIG9mIG56ZXJvIGNvZWZmaWNpZW50cwoKYGBge3J9Cm1ldF9hbXRfc3VtbWFyeV9sYW1iZGEgJT4lCiAgdW5uZXN0KGMobGFtYmRhLCBuemVybykpICU+JQogIGdyb3VwX2J5KGFscGhhKSAlPiUKICBmaWx0ZXIoYWJzKGxhbWJkYS5taW4ubWVhbi1sYW1iZGEpPT1taW4oYWJzKGxhbWJkYS5taW4ubWVhbi1sYW1iZGEpKSAgKSAlPiUKICB1bmdyb3VwKCkgJT4lCgpnZ3Bsb3QoYWVzKHg9YXMuY2hhcmFjdGVyKGFscGhhKSwgeT1uemVybykpICsKICBnZW9tX3BvaW50KCkgKwogIGdndGl0bGUoIk51bWJlciBvZiBub24temVybyBjb2VmZmljZW50cyBhdCBtaW5pbXVtIGxhbWJkYSIpICsKICB5bGltKDAsMzYpCmBgYApPSyBsZXQncyBkbyByZXBlYXRlZCB0ZXN0IHRyYWluIHN0YXJ0aW5nIGZyb20gdGhlc2UgQ1YgbGFtYmRhcwoKYGBge3J9Cm11bHRpX3R0IDwtIGZ1bmN0aW9uKGxhbWJkYSwgYWxwaGEsIG49MTAwMDAsIHNhbXBsZV9zaXplPTM2LCB0cmFpbl9zaXplPTMwLCB4LCB5PWxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkKSB7CiAgcHJpbnQobGFtYmRhKQogIHByaW50KGFscGhhKQp0dCA8LQogIHRpYmJsZShydW49MTpuKSAlPiUKICBtdXRhdGUodHJhaW49bWFwKHJ1biwgfiBzYW1wbGUoMTpzYW1wbGVfc2l6ZSwgdHJhaW5fc2l6ZSkpKSAlPiUKICBtdXRhdGUoZml0PW1hcCh0cmFpbiwgfiBnbG1uZXQoeD14Wy4sXSwgeT15Wy5dLCBsYW1iZGEgPSBsYW1iZGEsIGFscGhhID0gYWxwaGEgKSkpICU+JQogIAogIG11dGF0ZShwcmVkPW1hcDIoZml0LCB0cmFpbiwgfiBwcmVkaWN0KC54LCBuZXd4ID0geFstLnksXSkpKSAlPiUKICBtdXRhdGUoY29yPW1hcDJfZGJsKHByZWQsIHRyYWluLCB+IGNvcigueCwgeVstLnldKSAgKSkgJT4lCiAgbXV0YXRlKE1TRT1tYXAyX2RibChwcmVkLCB0cmFpbiwgfiBtZWFuKCh5Wy0ueV0gLSAueCleMikpKSAlPiUKICBzdW1tYXJpemUoCiAgICBudW1fbmE9c3VtKGlzLm5hKGNvcikpLCAKICAgIG51bV9sdF8wPXN1bShjb3I8PTAsIG5hLnJtPVRSVUUpLAogICAgYXZnX2Nvcj1tZWFuKGNvciwgbmEucm09VFJVRSksCiAgICBhdmdfTVNFPW1lYW4oTVNFKSkKdHQKfQoKYW10X2ZpdF90ZXN0X3RyYWluIDwtIG1ldF9hbXRfc3VtbWFyeV9sYW1iZGEgJT4lIAogIHNlbGVjdChhbHBoYSwgbGFtYmRhLm1pbi5tZWFuKQoKYW10X2ZpdF90ZXN0X3RyYWluIDwtIG1ldF9hbXRfbXVsdGlDViAlPiUKICBmaWx0ZXIocnVuPT0xKSAlPiUKICBzZWxlY3QoYWxwaGEsIGZpdCkgJT4lCiAgcmlnaHRfam9pbihhbXRfZml0X3Rlc3RfdHJhaW4pCgphbXRfZml0X3Rlc3RfdHJhaW4gPC0gYW10X2ZpdF90ZXN0X3RyYWluICU+JQogIG11dGF0ZShwcmVkX2Z1bGw9bWFwMihmaXQsIGxhbWJkYS5taW4ubWVhbiwgfiBwcmVkaWN0KC54LCBzPS55LCBuZXd4PW1ldF9hbXQuUENzKSksCiAgICAgICAgIGZ1bGxfUj1tYXBfZGJsKHByZWRfZnVsbCwgfiBjb3IoLngsIGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkKSksCiAgICAgICAgIGZ1bGxfTVNFPW1hcF9kYmwocHJlZF9mdWxsLCB+IG1lYW4oKGxlYWZsZW5ndGgkbGVhZl9hdmdfc3RkLS54KV4yKSkpICU+JQogIAogIG11dGF0ZSh0dD1tYXAyKGxhbWJkYS5taW4ubWVhbiwgYWxwaGEsIH4gbXVsdGlfdHQobGFtYmRhPS54LCBhbHBoYT0ueSwgeD1tZXRfYW10LlBDcykpKQoKCgooYW10X2ZpdF90ZXN0X3RyYWluIDwtIGFtdF9maXRfdGVzdF90cmFpbiAlPiUgdW5uZXN0KHR0KSkKYGBgCgpgYGB7cn0KYW10X2ZpdF90ZXN0X3RyYWluICU+JQogIGdncGxvdChhZXMoeD1hbHBoYSkpICsKICBnZW9tX2xpbmUoYWVzKHk9YXZnX2NvciksIGNvbG9yPSJyZWQiKSArCiAgZ2VvbV9wb2ludChhZXMoeT1hdmdfY29yKSwgY29sb3I9InJlZCIpICsKICBnZW9tX2xpbmUoYWVzKHk9YXZnX01TRSksIGNvbG9yPSJibHVlIikgKwogIGdlb21fcG9pbnQoYWVzKHk9YXZnX01TRSksIGNvbG9yPSJibHVlIikKYGBgCmFscGhhIG9mIDAuOCB0byAxLjAgYXJlIHZlcnkgc2ltaWxhciBhbmQgYXJlIHRoZSBiZXN0IGhlcmUuCgojIyBsb29rIGF0IGZpdDoKCmBgYHtyfQphbHBoYV9hbXQgPC0gLjgKCmJlc3RfYW10IDwtIGFtdF9maXRfdGVzdF90cmFpbiAlPiUgZmlsdGVyKGFscGhhID09IGFscGhhX2FtdCkgCmJlc3RfYW10X2ZpdCA8LSBiZXN0X2FtdCRmaXRbWzFdXQpiZXN0X2FtdF9sYW1iZGEgPC0gYmVzdF9hbXQkbGFtYmRhLm1pbi5tZWFuCgphbXRfY29lZi50YiA8LSBjb2VmKGJlc3RfYW10X2ZpdCwgcz1iZXN0X2FtdF9sYW1iZGEpICU+JSAKICBhcy5tYXRyaXgoKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JSAKICByb3duYW1lc190b19jb2x1bW4odmFyPSJQQyIpICU+JQogIHJlbmFtZShiZXRhPWAxYCkKICAKYW10X2NvZWYudGIgJT4lIGZpbHRlcihiZXRhIT0wKSAlPiUgYXJyYW5nZShiZXRhKQoKYGBgCgpwcmVkIGFuZCBvYnMKYGBge3J9CnBsb3QobGVhZmxlbmd0aCRsZWFmX2F2Z19zdGQsIGJlc3RfYW10JHByZWRfZnVsbFtbMV1dKQpjb3IudGVzdChsZWFmbGVuZ3RoJGxlYWZfYXZnX3N0ZCwgYmVzdF9hbXQkcHJlZF9mdWxsW1sxXV0pICMuNzM2CmJlc3RfYW10JGZ1bGxfTVNFCmBgYAoKIyMgUGVyY2VudCB2YXJpYW5jZSBleHBsYWluZWQKCmBgYHtyfQphbXRfdmFycyA8LSBhbXRfY29lZi50YiAlPiUgCiAgZmlsdGVyKGJldGEgIT0wLCBQQyE9IihJbnRlcmNlcHQpIikgJT4lCiAgcHVsbChQQykgJT4lIGMoImxlYWZfYXZnX3N0ZCIsIC4pCgphbXRfcmVsaW1wIDwtIGxlYWZsZW5ndGggJT4lIHNlbGVjdChsZWFmX2F2Z19zdGQpICU+JSBjYmluZChtZXRfYW10LlBDcykgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgZHBseXI6OnNlbGVjdChhbGxfb2YoYW10X3ZhcnMpKSAlPiUKICBjYWxjLnJlbGltcCgpIAoKYW10X2NvZWYudGIgPC0gYW10X3JlbGltcEBsbWcgJT4lIGFzLm1hdHJpeCgpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lCiAgcm93bmFtZXNfdG9fY29sdW1uKCJQQyIpICU+JQogIHJlbmFtZShQcm9wVmFyX21ldF9hbXQ9VjEpICU+JQogIGZ1bGxfam9pbihhbXRfY29lZi50YikgJT4lCiAgYXJyYW5nZShkZXNjKFByb3BWYXJfbWV0X2FtdCkpCgphbXRfY29lZi50YgoKYGBgCgoKIyMgdGVzdCBQQ3MgZm9yIHNpZyBhc3NvYyB3aXRoIHRydAoKIyMjIGxlYXZlcwpsbSB3aXRoIGd0IGFuZCB0cnQKYGBge3J9CmxtdGVzdCA8LSBtZXRfYW10LmxlYWZfUENBJHggJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdHJ0LCBzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgbXV0YXRlKHRydD1pZmVsc2Uoc3RyX2RldGVjdCh0cnQsICJkZWFkfEJMQU5LIiksICJkZWFkQkxBTksiLCB0cnQpKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1zdGFydHNfd2l0aCgiUEMiKSwgbmFtZXNfdG8gPSAiUEMiKSAlPiUKICBtdXRhdGUoUEM9c3RyX2MoImxlYWZfIiwgUEMpKSAlPiUKICBncm91cF9ieShQQykgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZShsbV9hZGQ9bWFwKGRhdGEsIH4gbG0odmFsdWUgfiBnZW5vdHlwZSArIHRydCwgZGF0YT0uKSksCiAgICAgICAgIGxtX2ludD1tYXAoZGF0YSwgfiBsbSh2YWx1ZSB+IGdlbm90eXBlKnRydCwgZGF0YT0uKSkpCgpgYGAKCmBgYHtyfQpsbXRlc3QgJT4lIG11dGF0ZShicm9vbXRpZHkgPSBtYXAobG1fYWRkLCB0aWR5KSkgJT4lCiAgdW5uZXN0KGJyb29tdGlkeSkgJT4lCiAgc2VsZWN0KFBDLCB0ZXJtLCBwLnZhbHVlKSAlPiUKICBmaWx0ZXIoISBzdHJfZGV0ZWN0KHRlcm0sICJJbnRlcmNlcHQiKSwKICAgICAgICAgcC52YWx1ZSA8IDAuMSkgJT4lCiAgYXJyYW5nZSh0ZXJtLCBwLnZhbHVlKQpgYGAKCmBgYHtyfQpsbXRlc3QgJT4lIG11dGF0ZShicm9vbXRpZHkgPSBtYXAobG1faW50LCBicm9vbTo6dGlkeSkpICU+JQogIHVubmVzdChicm9vbXRpZHkpICU+JQogIHNlbGVjdChQQywgdGVybSwgcC52YWx1ZSkgJT4lCiAgZmlsdGVyKCEgc3RyX2RldGVjdCh0ZXJtLCAiSW50ZXJjZXB0IiksCiAgICAgICAgIHAudmFsdWUgPCAwLjEpICU+JQogIGFycmFuZ2UodGVybSwgcC52YWx1ZSkKYGBgCgojIyMgcm9vdAoKYGBge3J9CmxtdGVzdCA8LSBtZXRfYW10LnJvb3RfUENBJHggJT4lCiAgYXMuZGF0YS5mcmFtZSgpICU+JQogIHJvd25hbWVzX3RvX2NvbHVtbigic2FtcGxlSUQiKSAlPiUKICBsZWZ0X2pvaW4obGVhZmxlbmd0aCkgJT4lCiAgc2VsZWN0KHNhbXBsZUlELCBnZW5vdHlwZSwgdHJ0LCBzdGFydHNfd2l0aCgiUEMiKSkgJT4lCiAgbXV0YXRlKHRydD1pZmVsc2Uoc3RyX2RldGVjdCh0cnQsICJkZWFkfEJMQU5LIiksICJkZWFkQkxBTksiLCB0cnQpKSAlPiUKICBwaXZvdF9sb25nZXIoY29scz1zdGFydHNfd2l0aCgiUEMiKSwgbmFtZXNfdG8gPSAiUEMiKSAlPiUKICBtdXRhdGUoUEM9c3RyX2MoInJvb3RfIiwgUEMpKSAlPiUKICBncm91cF9ieShQQykgJT4lCiAgbmVzdCgpICU+JQogIG11dGF0ZShsbV9hZGQ9bWFwKGRhdGEsIH4gbG0odmFsdWUgfiBnZW5vdHlwZSArIHRydCwgZGF0YT0uKSksCiAgICAgICAgIGxtX2ludD1tYXAoZGF0YSwgfiBsbSh2YWx1ZSB+IGdlbm90eXBlKnRydCwgZGF0YT0uKSkpCgpgYGAKCmBgYHtyfQpsbXRlc3QgJT4lIG11dGF0ZShicm9vbXRpZHkgPSBtYXAobG1fYWRkLCB0aWR5KSkgJT4lCiAgdW5uZXN0KGJyb29tdGlkeSkgJT4lCiAgc2VsZWN0KFBDLCB0ZXJtLCBwLnZhbHVlKSAlPiUKICBmaWx0ZXIoISBzdHJfZGV0ZWN0KHRlcm0sICJJbnRlcmNlcHQiKSwKICAgICAgICAgcC52YWx1ZSA8IDAuMSkgJT4lCiAgYXJyYW5nZSh0ZXJtLCBwLnZhbHVlKQpgYGAKCmBgYHtyfQpsbXRlc3QgJT4lIG11dGF0ZShicm9vbXRpZHkgPSBtYXAobG1faW50LCBicm9vbTo6dGlkeSkpICU+JQogIHVubmVzdChicm9vbXRpZHkpICU+JQogIHNlbGVjdChQQywgdGVybSwgcC52YWx1ZSkgJT4lCiAgZmlsdGVyKCEgc3RyX2RldGVjdCh0ZXJtLCAiSW50ZXJjZXB0IiksCiAgICAgICAgIHAudmFsdWUgPCAwLjEpICU+JQogIGFycmFuZ2UodGVybSwgcC52YWx1ZSkKYGBgCgpDaGVja291dCB0aGUgcm90YXRpb25zLiAgCgpgYGB7cn0KbWV0X2FtdF9yb3RhdGlvbl9vdXQgPC0gbWV0X2FtdC5QQ19yb3RhdGlvbiAlPiUgCiAgcGl2b3RfbG9uZ2VyKC1tZXRhYm9saXRlLCBuYW1lc190bz0iUEMiLCB2YWx1ZXNfdG89ImxvYWRpbmciKSAlPiUKICBmaWx0ZXIoUEMgJWluJSBmaWx0ZXIoYW10X2NvZWYudGIsIGJldGEhPTApJFBDICkgJT4lCiAgZ3JvdXBfYnkoUEMpICU+JQogICAgZmlsdGVyKCFzdHJfZGV0ZWN0KG1ldGFib2xpdGUsIi4qKGxlYWZ8cm9vdClfWzAtOV0qJCIpKSAlPiUKICBmaWx0ZXIoYWJzKGxvYWRpbmcpID49IDAuMDUpICU+JQogIGxlZnRfam9pbihhbXRfY29lZi50YiwgYnk9IlBDIikgJT4lCiAgYXJyYW5nZShkZXNjKGFicyhiZXRhKSksIGRlc2MoYWJzKGxvYWRpbmcpKSkgJT4lCiAgbXV0YXRlKG9yZ2FuPWlmZWxzZShzdHJfZGV0ZWN0KG1ldGFib2xpdGUsICJfbGVhZl8iKSwgImxlYWYiLCAicm9vdCIpLAogICAgICAgICB0cmFuc2Zvcm1hdGlvbj0icmF3IiwKICAgICAgICAgbWV0YWJvbGl0ZT1zdHJfcmVtb3ZlKG1ldGFib2xpdGUsICJtZXRfYW10Xyhyb290fGxlYWYpXyIpLAogICAgICAgICBtZXRhYm9saXRlX2VmZmVjdF9vbl9sZWFmPWlmZWxzZShiZXRhKmxvYWRpbmc+MCwgImluY3JlYXNlIiwgImRlY3JlYXNlIikpCm1ldF9hbXRfcm90YXRpb25fb3V0ICU+JSAgd3JpdGVfY3N2KCIuLi9vdXRwdXQvTGVhZl9hc3NvY2lhdGVkX21ldGFib2xpdGVzX3Jhdy5jc3YiKQoKbWV0X2FtdF9yb3RhdGlvbl9vdXQKYGBgCgo=